Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Mathematisches
6 Eigene Klassen schreiben
7 Exceptions
8 Die Funktionsbibliothek
9 Threads und nebenläufige Programmierung
10 Raum und Zeit
11 Datenstrukturen und Algorithmen
12 Dateien und Datenströme
13 Die eXtensible Markup Language (XML)
14 Grafische Oberflächen mit Swing
15 Grafikprogrammierung
16 Das Netz
17 JavaServer Pages und Servlets
18 Verteilte Programmierung mit RMI und Web–Services
19 Applets, Midlets und Sound
20 Datenbankmanagement mit JDBC
21 Reflection und Annotationen
22 Komponenten durch Bohnen
23 Logging und Monitoring
24 Sicherheitskonzepte
25 Java Native Interface (JNI)
26 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Index

Download:
- ZIP, ca. 12,5 MB
Buch bestellen

Website zum Buch
Weblog des Autors
Ihre Meinung?

Spacer
 <<   zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren mit der Java Standard Edition Version 6
Buch: Java ist auch eine Insel

Java ist auch eine Insel
6., akt. und erw. Aufl., mit DVD
1.454 S., 49,90 Euro
Galileo Computing
ISBN 3-89842-838-9
gp 6 Eigene Klassen schreiben
  gp 6.1 Eigene Klassen deklarieren
    gp 6.1.1 Methodenaufrufe und Nebeneffekte
    gp 6.1.2 Argumentübergabe mit Referenzen
    gp 6.1.3 Die this-Referenz
    gp 6.1.4 Überdeckte Objektvariablen nutzen
  gp 6.2 Privatsphäre und Sichtbarkeit
    gp 6.2.1 Wieso nicht freie Methoden und Variablen für alle?
    gp 6.2.2 Privat ist nicht ganz privat: Es kommt darauf an, wer’s sieht
    gp 6.2.3 Zugriffsmethoden für Attribute deklarieren
  gp 6.3 Statische Methoden und statische Attribute
    gp 6.3.1 Warum statische Eigenschaften sinnvoll sind
    gp 6.3.2 Statische Eigenschaften mit static
    gp 6.3.3 Statische Eigenschaften über Referenzen nutzen?
    gp 6.3.4 Warum die Groß- und Kleinschreibung wichtig ist
    gp 6.3.5 Statische Eigenschaften und Objekteigenschaften
    gp 6.3.6 Statische Variablen zum Datenaustausch
    gp 6.3.7 Statische Blöcke als Klasseninitialisierer
  gp 6.4 Konstanten und Aufzählungen
    gp 6.4.1 Konstanten über öffentliche statische final-Variablen
    gp 6.4.2 Eincompilierte Belegungen der Klassenvariablen
    gp 6.4.3 Typsicherere Konstanten
    gp 6.4.4 Aufzählungen mit enum
    gp 6.4.5 enum-Konstanten in switch
    gp 6.4.6 Enum-Objekte in der Weitergabe
    gp 6.4.7 Statische Imports von Aufzählungen
  gp 6.5 Objekte anlegen und zerstören
    gp 6.5.1 Konstruktoren schreiben
    gp 6.5.2 Konstruktor nimmt ein Objekt vom Typ der eigenen Klasse (Copy–Konstruktor)
    gp 6.5.3 Einen anderen Konstruktor der gleichen Klasse aufrufen
    gp 6.5.4 Initialisierung der Objekt- und Klassenvariablen
    gp 6.5.5 Finale Werte im Konstruktor und in statischen Blöcken setzen
    gp 6.5.6 Exemplarinitialisierer (Instanzinitialisierer)
    gp 6.5.7 Ihr fehlt uns nicht – der Garbage-Collector
    gp 6.5.8 Implizit erzeugte String-Objekte
    gp 6.5.9 Private Konstruktoren, Utility-Klassen, Singleton, Fabriken
  gp 6.6 Assoziationen zwischen Objekten
    gp 6.6.1 Gegenseitige Abhängigkeiten von Klassen
  gp 6.7 Vererbung
    gp 6.7.1 Vererbung in Java
    gp 6.7.2 Einfach- und Mehrfachvererbung
    gp 6.7.3 Gebäude modelliert
    gp 6.7.4 Konstruktoren in der Vererbung
    gp 6.7.5 Sichtbarkeit protected
    gp 6.7.6 Das Substitutionsprinzip
    gp 6.7.7 Automatische und explizite Typanpassung
    gp 6.7.8 Typen mit dem binären Operator instanceof testen
    gp 6.7.9 Array-Typen und Kovarianz
    gp 6.7.10 Methoden überschreiben
    gp 6.7.11 Mit super eine Methode der Oberklasse aufrufen
    gp 6.7.12 Kovariante Rückgabetypen
    gp 6.7.13 Finale Klassen
    gp 6.7.14 Nicht überschreibbare Funktionen
    gp 6.7.15 Zusammenfassung zur Sichtbarkeit
    gp 6.7.16 Sichtbarkeit in der UML
    gp 6.7.17 Zusammenfassung: Konstruktoren und Methoden
  gp 6.8 Die Oberklasse gibt Funktionalität vor
    gp 6.8.1 Spätes dynamisches Binden als Beispiel für Polymorphie
    gp 6.8.2 Unpolymorph bei privaten, statischen und finalen Methoden
    gp 6.8.3 Polymorphie bei Konstruktoraufrufen
  gp 6.9 Abstrakte Klassen und abstrakte Methoden
    gp 6.9.1 Abstrakte Klassen
    gp 6.9.2 Abstrakte Methoden
  gp 6.10 Schnittstellen
    gp 6.10.1 Ein Polymorphie-Beispiel mit Schnittstellen
    gp 6.10.2 Die Mehrfachvererbung bei Schnittstellen
    gp 6.10.3 Erweitern von Interfaces – Subinterfaces
    gp 6.10.4 Vererbte Konstanten bei Schnittstellen
    gp 6.10.5 Schnittstellenmethoden, die nicht implementiert werden müssen
    gp 6.10.6 Abstrakte Klassen und Schnittstellen im Vergleich
    gp 6.10.7 CharSequence als Beispiel einer Schnittstelle
    gp 6.10.8 Die Schnittstelle Iterable
  gp 6.11 Object ist die Mutter aller Oberklassen
    gp 6.11.1 Klassenobjekte
    gp 6.11.2 Objektidentifikation mit toString()
    gp 6.11.3 Objektgleichheit mit equals() und Identität
    gp 6.11.4 Klonen eines Objekts mit clone()
    gp 6.11.5 Hashcodes über hashCode() liefern
    gp 6.11.6 Aufräumen mit finalize()
    gp 6.11.7 Synchronisation
  gp 6.12 Innere Klassen
    gp 6.12.1 Statische innere Klassen und Schnittstellen
    gp 6.12.2 Mitglieds- oder Elementklassen
    gp 6.12.3 Lokale Klassen
    gp 6.12.4 Anonyme innere Klassen
    gp 6.12.5 this und Vererbung
    gp 6.12.6 Implementierung einer verketteten Liste
    gp 6.12.7 Funktionszeiger
  gp 6.13 Generische Datentypen
    gp 6.13.1 Einfache Klassenschablonen
    gp 6.13.2 Einfache Methodenschablonen
    gp 6.13.3 Umsetzen der Generics, Typlöschung und Raw-Types
    gp 6.13.4 Einschränken der Typen
    gp 6.13.5 Generics und Vererbung, Invarianz
    gp 6.13.6 Wildcards
  gp 6.14 Die Spezial-Oberklasse Enum
    gp 6.14.1 Methoden auf Enum-Objekten
    gp 6.14.2 enum mit eigenen Konstruktoren und Methoden
  gp 6.15 Dokumentationskommentare mit JavaDoc
    gp 6.15.1 Einen Dokumentationskommentar setzen
    gp 6.15.2 Mit javadoc eine Dokumentation erstellen
    gp 6.15.3 HTML-Tags in Dokumentationskommentaren
    gp 6.15.4 Generierte Dateien
    gp 6.15.5 Dokumentationskommentare im Überblick
    gp 6.15.6 JavaDoc und Doclets
    gp 6.15.7 Veraltete (deprecated) Klassen, Konstruktoren und Methoden


Galileo Computing

6.12 Innere Klassen  downtop

Bisher haben wir nur Klassen kennen gelernt, die entweder in Paketen organisiert waren oder in einer Datei. Diese Form von Klassen heißen »Top-Level-Klassen«. Es gibt darüber hinaus die Möglichkeit, eine Klasse in eine andere Klasse hineinzunehmen und sie damit noch enger aneinander zu binden. Eine Klasse, die so eingebunden wird, heißt »innere Klasse«. Im Allgemeinen sieht dies wie folgt aus:

class Außen { 
  class Innen { 
  } 
}

Die Java-Spezifikation beschreibt vier Typen von inneren Klassen, die im Folgenden beschrieben werden. Gleichgültig auf welche Weise sie deklariert werden, so müssen sich darf doch der Name des inneren Typs vom Namen des äußeren Typs unterscheiden.


Galileo Computing

6.12.1 Statische innere Klassen und Schnittstellen  downtop

Die einfachste Variante einer inneren Klasse oder Schnittstelle wird wie eine statische Eigenschaft in die Klasse eingesetzt und heißt statische innere Klasse. Wegen der Schachtelung wird dieser Typ im Englischen nested top-level class genannt. Die Namensgebung betont mit dem Begriff top-level, dass die Klassen das Gleiche können wie »normale« Klassen oder Schnittstellen nur bilden sie quasi ein kleines Unterpaket mit eigenem Namensraum. Insbesondere sind zur Erzeugung von Exemplaren von inneren Klassen keine Objekte der äußeren Klasse nötig. Sun betont in der Spezifikation der Sprache, dass die statischen inneren Klassen keine »echten« inneren Klassen sind, doch soll uns das im Folgenden egal sein.

Deklarieren wir Lampe als äußere Klasse und Birne als eine innere statische Klasse.

Listing 6.74    Lampe.java

public class Lampe 
{ 
  static String s = "Huhu"; 
  int i = 1; 
 
  static class Birne 
  { 
    void grüßGott() 
    { 
      System.out.println( s ); 
//      System.out.println( i );          // Fehler, da i nicht statisch 
    } 
  } 
 
  public static void main( String[] args ) 
  { 
    Birne birne = new Lampe.Birne();  // oder Birne.Lampe birne = ... 
    birne.grüßGott(); 
  } 
}

Die statischen innere Klasse Birne besitzt Zugriff auf alle anderen statischen Eigenschaften der äußeren Klasse Lampe, in unserem Fall die Variable s.  Ein Zugriff auf Objektvariablen ist aus der statischen inneren Klasse nicht möglich, da sie als extra Klasse gezählt wird, die im gleichen Paket liegt. Der Zugriff von außen auf innere Klassen gelingt mit der Schreibweise ÄußereKlasse.InnereKlasse; der Punkt wird also so verwendet, wie wir es von den Paketen als Namensraum gewöhnt sind. Die innere Klasse muss einen anderen Namen als die äußere haben. Es sind die Modifizierer abstract, final und Sichtbarkeitsmodifizierer erlaubt.

Umsetzung der inneren Klassen

Es ist eine gelungene Arbeit der Sun-Entwickler, die Einführung von inneren Klassen ohne Änderung der virtuellen Maschine über die Bühne gebracht zu haben. Der Compiler generiert aus den inneren Klassen nämlich einfach normale Klassen, die jedoch mit einigen Spezialfunktionen ausgestattet sind. Für die geschachtelten inneren Klassen generiert der Compiler neue Namen nach dem Muster: ÄußereKlasse$InnereKlasse, das heißt, ein Dollar-Zeichen trennt die Namen von äußerer und innerer Klasse.

Die weiteren inneren Typen, die wir kennen lernen wollen, sind alle nicht statisch und benötigen ein Verweis auf das äußere Objekt.


Galileo Computing

6.12.2 Mitglieds- oder Elementklassen  downtop

Eine Mitgliedsklasse (engl. member class), auch Elementklasse genannt, ist ebenfalls vergleichbar mit einem Attribut, nur ist es nicht statisch. (Statische innere Klassen lassen sich aber auch als statische Mitgliedsklassen bezeichnen.) Die innere Klasse kann zusätzlich auf alle Attribute der äußeren Klasse zugreifen. Dazu zählen auch die privaten Eigenschaften, eine Designentscheidung, die sehr umstritten ist und kontrovers diskutiert wird.

Deklarieren wir eine innere Mitgliedsklasse Muster in Rahmen:

Listing 6.75    Rahmen.java

public class Rahmen 
{ 
  String s = "kringelich"; 
 
  class Muster 
  { 
    void standard() 
    { 
      System.out.println( s ); 
    } 
//    static void immer()  {  }   // Fehler 
  } 
}

Ein Exemplar der Klasse Muster hat Zugriff auf alle Eigenschaften von Rahmen. Um innerhalb der äußeren Klasse Rahmen ein Exemplar von Muster zu erzeugen, muss ein Exemplar der äußeren Klasse existieren. Das ist eine wichtige Unterscheidung gegenüber den statischen inneren Klassen von weiter oben. Statische innere Klassen existieren auch ohne Objekt der äußeren Klasse. Eine zweite wichtige Eigenschaft ist, dass innere Mitgliedsklassen selbst keine statischen Eigenschaften deklarieren dürfen.

Von außen Exemplare innerer Klassen erzeugen

Innerhalb der äußeren Klassen kann einfach mit dem new-Operator ein Exemplar der inneren Klasse erzeugt werden. Kommen wir von außerhalb und wollen Exemplare der inneren Klasse erzeugen, so müssen wir bei Elementklassen sicherstellen, dass es ein Exemplar der äußeren Klasse gibt. Die Sprache schreibt eine neue Form für die Erzeugung mit new vor, die das allgemeine Format

referenz.new InnereKlasse(...)

besitzt. Dabei ist ref eine Referenz der äußeren Klasse.

Nehmen wir an, eine Klasse Haus besitzt die innere Element-Klasse Zimmer:

class Haus 
{ 
  class Zimmer 
  { 
  } 
}

Um von außen ein Objekt von Zimmer aufzubauen, schreiben wir:

Haus h = new Haus(); 
Zimmer z = h.new Zimmer();

oder auch in einer Zeile:

Zimmer z = new Haus().new Zimmer();

Die this-Referenz

Möchte eine innere Klasse In auf die this-Referenz der umgebenden Klasse Out zugreifen, schreiben wir Out.this. Wenn sich Variablen überdecken, so schreiben wir Out.this.Eigenschaft, um an die Eigenschaften der äußeren Klasse zu gelangen.

Listing 6.76    Haus.java

class Haus 
{ 
  String s = "Haus"; 
 
  class Zimmer 
  { 
    String s = "Zimmer"; 
 
    class Stuhl 
    { 
      String s = "Stuhl"; 
 
      void ausgabe() 
      { 
        System.out.println( s );               // Stuhl 
        System.out.println( this.s );          // Stuhl 
        System.out.println( Stuhl.this.s );    // Stuhl 
        System.out.println( Zimmer.this.s );   // Zimmer 
        System.out.println( Haus.this.s );     // Haus 
      } 
    } 
  } 
 
  public static void main( String[] args ) 
  { 
    new Haus().new Zimmer().new Stuhl().ausgabe(); 
  } 
}

Hinweis Elementklassen können beliebig geschachtelt sein, und da der Name eindeutig ist, gelangen wir mit Klassenname.this immer an die jeweilige Eigenschaft.

Betrachten wir das obere Beispiel, dann lassen sich Objekte für die inneren Klassen Haus, Zimmer und Stuhl wie folgt erstellen:

Haus a = new Haus;                    // Exemplar von Haus 
Haus.Zimmer b = a.new Zimmer();       // Exemplar von Zimmer in a 
Haus.Zimmer.Stuhl c = b.new Stuhl();  // Exemplar von Stuhl in b 
c.ausgabe();                          // Methode von Stuhl

Damit ist auch deutlich geworden, dass die Qualifizierung mit dem Punkt bei Haus.Zimmer.Stuhl nicht automatisch bedeutet, dass Haus ein Paket mit dem Unterpaket Zimmer ist, in dem die Klasse Stuhl existiert. Das macht es für die Lesbarkeit nicht gerade einfacher, und es droht eine Verwechslungsgefahr zwischen inneren Klassen und Paketen. Deshalb sollte die Namenskonvention befolgt werden: Klassennamen beginnen mit Großbuchstaben, Paketnamen mit Kleinbuchstaben.

Vom Compiler generierten Klassendateien

Für das Beispiel Rahmen und Muster erzeugt der Compiler die Dateien Rahmen.class und Rahmen$Muster.class. Damit ohne Änderung der virtuellen Maschine die innere Klasse an die Attribute der äußeren kommt, wird in jedem Exemplar der inneren Klasse eine Referenz auf das zugehörige Objekt der äußeren Klasse gelegt. Damit kann die innere Klasse auch auf nichtstatische Attribute der äußeren Klasse zugreifen. Für die innere Klasse ergibt sich folgendes Bild in Rahmen$Muster.class:

class Rahmen$Muster 
{ 
  private Rahmen this$0; 
  // ... 
}

Die Variable this$0 ist eine Kopie der Referenz auf Rahmen.this. Die Konstruktoren der inneren Klasse erhalten einen zusätzlichen Parameter vom Typ Rahmen, mit dem die this$0-Variable initialisiert wird.


Galileo Computing

6.12.3 Lokale Klassen  downtop

Lokale Klassen sind auch innere Klassen, die jedoch nicht als Eigenschaft direkt in einer Klasse eingesetzt werden. Diese Form der inneren Klasse befindet sich in Anweisungsblöcken von Methoden oder Initialisierungsblöcken. Lokale Schnittstellen sind nicht möglich. So soll im folgenden Beispiel die main()-Methode eine innere Klasse mit einem Konstruktor besitzen, der auf die finale Variable j zugreift.

Listing 6.77    DrinnenMachtSpass.java

public class DrinnenMachtSpass 
{ 
  public static void main( String[] args ) 
  { 
    int i = 2; 
    final int j = 3; 
 
    class In 
    { 
      In() { 
        System.out.println( j ); 
//        System.out.println( i );    // Fehler, da i nicht final 
      } 
    } 
    new In(); 
  } 
}

Die Deklaration der inneren Klasse In ist wie eine Anweisung eingesetzt. Ein Sichtbarkeitsmodifzierer ist ungültig und die Klasse darf keine Klassenmethoden und allgemeine statische Variablen (Konstanten schon) deklarieren. Jede lokale Klasse kann auf Methoden der äußeren Klasse zugreifen und zusätzlich auf die lokalen Variablen und Parameter, die mit dem Modifizierer final als unveränderlich ausgezeichnet sind. Liegt die innere Klasse in einer statischen Funktion, kann sie jedoch keine Objektmethode aufrufen. Eine weitere Einschränkung im Vergleich zu den Elementklassen ist, dass die Modifizierer public, protected, private und static nicht erlaubt sind.


Galileo Computing

6.12.4 Anonyme innere Klassen  downtop

Anonyme Klassen gehen noch einen Schritt weiter als lokale Klassen. Sie haben keinen Namen und erzeugen immer automatisch ein Objekt. Klassendeklaration und Objekterzeugung sind zu einem Sprachkonstrukt verbunden. Die allgemeine Notation ist folgende:

new KlasseOderSchnittstelle() { /* Eigenschaften der inneren Klasse */ }

In dem Block geschweifter Klammern lassen sich nun Methoden und Attribute deklarieren oder Methoden überschreiben. Hinter new steht der Name einer Klasse oder Schnittstelle.

  • new Klassenname(Argumente) { . }. Steht hinter new ein Klassentyp, dann ist die anonyme Klasse eine Unterklasse von Klassenname. Es lassen sich mögliche Argumente für den Konstruktor der Basisklasse angeben, den die anonyme automatisch mit super() aufruft.
  • new Schnittstellenname() { . }. Steht hinter new der Name einer Schnittstelle, dann erbt die anonyme Klasse von Object und implementiert die Schnittstelle Schnittstellenname. Implementiert sie nicht die Operationen der Schnittstelle ist das ein Fehler; wir hätten nichts davon, denn dann hätten wir eine abstrakte innere Klasse, von der kein Objekt erzeugt werden könnte.

Für anonyme innere Klassen gilt die Einschränkung, dass keine zusätzlichen extends- oder implements-Angaben möglich sind. Ebenso sind keine eigenen Konstruktoren möglich.

Wir wollen eine innere Klasse schreiben, die Unterklasse von java.awt.Point ist. Sie soll die toString()-Methode überschreiben.

Listing 6.78    InnerToStringPoint.java

import java.awt.Point; 
 
public class InnerToStringPoint 
{ 
  public static void main( String[] args ) 
  { 
    Point p = new Point( 10, 12 ) {       // Anonyme Klasse extends Point 
      @Override 
      public String toString() { 
        return "(" + x + "," + y + ")"; 
      } 
    }; 
    System.out.println( p );    // (10,12) 
  } 
}

Da sofort eine Unterklasse von Point aufgebaut wird, fehlt der Name der inneren Klasse. Das einzige Exemplar dieser anonymen Klasse lässt sich über die Variable p weiterverwenden.


Hinweis Eine innere Klasse kann Methoden der Oberklasse überschreiben oder Operationen aus Schnittstellen implementieren. Neue Eigenschaften anzubieten, wäre zwar zulässig, aber nicht sinnvoll, es sei denn, die innere Klasse selbst nutzt die Methoden – dann müssen sie aber unsichtbar sein. Von außen sind Methoden, die nicht in der Oberklasse beziehungsweise Schnittstelle bekannt sind, nicht sichtbar. Deshalb sind auch anonyme Unterklassen von Object (ohne weitere implementierte Schnittstellen) nutzlos. (Wir lassen die Tatsache, dass eine Anwendung mit Reflection auf die Methoden zugreifen kann, außen vor.)

Umsetzung innerer anonymer Klassen

Auch für innere anonyme Klassen erzeugt der Compiler eine normale Klassendatei. Wir haben gesehen, dass im Fall einer »normalen« inneren Klasse die Notation ÄußereKlasse$InnereKlasse gewählt wird. Das klappt bei anonymen inneren Klassen natürlich nicht mehr, da uns der Name der inneren Klasse fehlt. Der Compiler wählt daher folgende Notation für Klassennamen: InnerToStringDate$1. Falls es mehr als eine innere Klasse gibt, wird 1 zu 2, 2 zu 3 und so weiter.

Nutzung innerer Klassen für Threads

Sehen wir uns ein weiteres Beispiel für die Implementierung von Schnittstellen an. Um nebenläufige Programme zu implementieren, gibt es die Klasse Thread und die Schnittstelle Runnable. (Für das Beispiel greifen wir vor; Threads werden in Kapitel 9, »Threads und nebenläufige Programmierung«, genau beschrieben.)

Die Schnittstelle Runnable schreibt eine Operation run() vor, in der der parallel abzuarbeitende Programmcode gesetzt wird. Das geht gut mit einer inneren anonymen Klasse, die Runnable implementiert.

new Runnable() {     // Anonyme Klasse extends Object implements Runnable 
  public void run() { 
    ... 
  } 
};

Das Exemplar kommt in den Konstruktor der Klasse Thread. Der Thread wird mit start() angekurbelt. Damit folgt zusammengesetzt und mit Implementierung von run():

new Thread( new Runnable() { 
  public void run() { 
    for ( int i = 0; i < 10; i++ ) 
      System.out.println( i ); 
  }; 
} ).start(); 
 
for ( int i = 0; i < 10; i++ ) 
  System.out.println( i );

In der Ausgabe wird zum Beispiel Folgendes erscheinen (hier komprimiert):

0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 9 9

Der erste Thread beginnt recht schnell seine Zahlen auszugeben. Doch an der doppelten Zahl 9 sehen wir, dass die beiden Teile wirklich parallel arbeiten. Ausführliche Informationen finden sich in Kapitel 9.

(Strg) + (Leertaste) nach der geschweiften Klammer listet eine Reihe von Methoden auf, die wir uns von Eclipse implementieren lassen können. Da entscheiden wir uns doch für run().

Konstruktoren innerer anonymer Klassen

Da anonyme Klassen keinen Namen haben, muss für Konstruktoren ein anderer Weg gefunden werden. Hier helfen Exemplarinitialisierungsblöcke, das heißt die Blöcke in geschweiften Klammern direkt innerhalb einer Klasse.

Dazu ein Beispiel: Die anonyme Klasse ist eine Unterklasse von Point und initialisiert im Konstruktor einen Punkt mit den Koordinaten –1, –1. Aus diesem speziellen Punkt-Objekt lesen wir dann die Koordinaten wieder aus.

Listing 6.79    AnonymousAndInside.java, main()

java.awt.Point p = new java.awt.Point() { { x = -1; y = -1; } }; 
 
System.out.println( p.getLocation() );  // java.awt.Point[x=-1,y=-1] 
 
System.out.println( new java.awt.Point( -1, 0 ) 
{ 
  { 
    y = -1; 
  } 
}.getLocation() );                      // java.awt.Point[x=-1,y=-1]

super()

Innerhalb eines »anonymen Konstruktors« kann kein super() verwendet werden, um den Konstruktor der Oberklasse aufzurufen. Dies liegt daran, dass automatisch ein super() in den Initialisierungsblock eingesetzt wird. Die Parameter für die gewünschte Variante des (überladenen) Oberklassen-Konstruktors werden am Anfang der Deklaration der anonymen Klasse angegeben. Dies zeigt das zweite Beispiel im Programm AnonymUndInnen.java:

System.out.println( new Point(-1, 0) { { y = -1; } }.getLocation() );

Beispiel Wir initialisieren ein Objekt BigDecimal, das beliebig große Ganzzahlen aufnehmen kann. Im Konstruktor der anonymen Unterklasse geben wir anschließend den Wert mit der geerbten toString()-Methode aus:
new java.math.BigDecimal( "12345678901234567890" ) { 
  { System.out.println( toString() ); } 
};


Galileo Computing

6.12.5 this und Vererbung  downtop

Wenn wir ein qualifiziertes this verwenden, dann bezeichnet C.this die äußere Klasse, also das umschließende Exemplar. Gilt jedoch die Beziehung C1.C2. ... Ci. ... Cn., haben wir mit Ci.this ein Problem, wenn Ci eine Oberklasse von Cn ist. Es geht also um den Fall, dass eine textuell umgebende Klasse zugleich auch Oberklasse ist. Das eigentliche Problem besteht darin, dass hier zweidimensionale Namensräume hierarchisch kombiniert werden. Die eine Dimension sind die Bezeichner beziehungsweise Methoden aus den lexikalisch umgebenden Klassen, die andere Dimension sind die ererbten Eigenschaften aus der Oberklasse. Hier sind beliebige Überlappungen und Mehrdeutigkeiten denkbar. Durch diese ungenaue Beziehung zwischen inneren Klassen und Vererbung kam es unter JDK 1.1 und 1.2 zu unterschiedlichen Ergebnissen.

Im nächsten Beispiel soll von der Klasse Schuh die innere Klasse Fuss den Schuh erweitern und die Methode wasBinIch() überschreiben.

Listing 6.80    Schuh.java

public class Schuh 
{ 
  void wasBinIch() 
  { 
    System.out.println( "Ich bin der Schuh des Manitu" ); 
  } 
 
  class Fuss extends Schuh 
  { 
    void spannung() 
    { 
      Schuh.this.wasBinIch(); 
    } 
 
    @Override 
    void wasBinIch() 
    { 
      System.out.println( "Ich bin ein Schuh.Fuss" ); 
    } 
  } 
 
  public static void main( String[] args ) 
  { 
    new Schuh().new Fuss().spannung(); 
  } 
}

Legen wir in der main()-Funktion ein Objekt der Klasse Fuss an, dann landen wir in der Klasse Fuss und nicht in Schuh. Das heißt, die Ausgabe ist:

Ich bin der Schuh des Manitu

Das bedeutet, dass in spannung() durch Schuh.this zwar das zum Fuss-Objekt gehörende Schuh-Exemplar gemeint ist, wir aber durch die Überschreibung dennoch in der Methode aus der Klasse Fuss landen. Vor 1.2 kam als Ergebnis die erste Zeichenkette heraus. Das Ergebnis unter JDK 1.2 ist analog zu ((Schuh)this).wasBinIch().


Galileo Computing

6.12.6 Implementierung einer verketteten Liste  downtop

Verkettete Listen gibt es in Java seit Java 1.2 über die Klasse LinkedList, sodass wir die Implementierung eigentlich nicht betrachten müssten. Da es für viele Leser jedoch noch ein Geheimnis ist, wie die dazu benötigten Pointer in Java abgebildet werden, sehen wir uns eine einfache Implementierung an. Zunächst benötigen wir eine Zelle, die Daten und eine Referenz auf das folgende Listenelement speichert. Die Zelle wird durch die Klasse Cell modelliert. Im UML-Diagramm taucht die innere Klasse im letzten Block auf.

Listing 6.81    LinkedList.java, Teil 1

public class LinkedList 
{ 
  private Cell head, tail; 
 
  static class Cell 
  { 
    Object data; 
    Cell   next; 
  } 
 
  public void add( Object o ) 
  { 
    Cell newCell = new Cell(); 
    newCell.data = o; 
 
    if ( head == null )  // oder tail == null 
      head = tail = newCell; 
    else 
      tail = tail.next = newCell; 
  } 
 
  public void addAll( Object... os ) 
  { 
    for ( Object o : os ) 
      add( o ); 
  } 
 
  @Override 
  public String toString() 
  { 
    StringBuilder sb = new StringBuilder( 1024 ).append( '[' ); 
 
    for ( Cell cell = head; cell != null; ) 
    { 
      sb.append( cell.data ); 
 
      if ( cell.next != null ) 
        sb.append( ", " ); 
      cell = cell.next; 
    } 
 
    return sb.append( ']' ).toString(); 
  } 
}

Eine verkettete Liste besteht aus einer Menge von Cell-Elementen. Da diese Objekte fest mit der Liste verbunden sind, ist hier der Einsatz von geschachtelten Klassen sinnvoll. Cell ist hier statisch, kann aber auch Elementklasse sein, doch ist das egal, weil die Klasse von außen nicht sichtbar ist.

Die Liste benötigt zum Einfügen einen Verweis auf den Kopf (erstes Element) und auf das Ende (letztes Element). Um nun ein Element dieser Liste hinzuzufügen, erzeugen wir zunächst eine neue Zelle newCell. Ist tail oder head gleich null, bedeutet dies, dass es noch keine Elemente in der Liste gibt. Danach legen wir die Referenzen für Listenanfang und –ende auf das neue Objekt. Werden nun später Elemente eingefügt, hängen sie sich hinter tail. Wenn es schon Elemente in der Liste gibt, dann ist head oder tail nicht gleich null, und tail zeigt auf das letzte Element. Seine next-Referenz zeigt auf null und wird dann mit einem neuen Wert belegt, nämlich mit dem des neu beschafften Objekts newCell. Nun hängt es in der Liste, und das Ende muss noch angepasst werden. Daher legen wir die Referenz tail auch noch auf das neue Objekt.

Listing 6.82    LinkedListDemo.java

public class LinkedListDemo 
{ 
  public static void main( String[] args ) 
  { 
    LinkedList l = new LinkedList(); 
    l.addAll( "Hallo", "Otto" ); 
 
    System.out.println( l ); 
  } 
}

Galileo Computing

6.12.7 Funktionszeiger  toptop

Das folgende Beispiel implementiert Funktionszeiger über Schnittstellen. Es beginnt mit der Markierungsschnittstelle Operator.

Listing 6.83    functions/Operator.java

package functions; 
 
public interface Operator 
{ 
  // Markierungsschnittstelle 
}

Sie soll Basis-Schnittstelle für Operatoren sein. Von dieser Schnittstelle wollen wir Binary-Operator ableiten, eine Schnittstelle mit einer Operation für zweistellige Operatoren.

Listing 6.84    functions/BinaryOperator.java

package functions; 
 
public interface BinaryOperator extends Operator 
{ 
  double calc( double a, double b ); 
}

Zum Test sollen die Operatoren für + und * implementiert werden:

Listing 6.85    functions/MulOperator.java

package functions; 
 
public class MulOperator implements BinaryOperator 
{ 
  public double calc( double a, double b ) 
  { 
    return a * b; 
  } 
}

Listing 6.86    functions/AddOperator.java

package functions; 
 
public class AddOperator implements BinaryOperator 
{ 
  public double calc( double a, double b ) 
  { 
    return a + b; 
  } 
}

Eine Sammlung von Operatoren speichert ein Operator-Manager. Bei ihm können wir dann über eine Kennung ein Berechnungsobjekt beziehen:

Listing 6.87    functions/OperatorManager.java

package functions; 
 
public class OperatorManager 
{ 
  public final static int ADD = 0; 
  public final static int MUL = 1; 
 
  private static Operator[] operators = { 
    new AddOperator(), 
    new MulOperator() 
  }; 
 
  public static Operator getOperator( int id ) 
  { 
    return operators[ id ]; 
  } 
}

Wenn wir nun einen Operator wünschen, so fragen wir den OperatorManager nach dem passenden Objekt. Die Rückgabe wird ein Operator-Objekt sein, was wir auf BinaryOperator anpassen, da der Basistyp keine Funktionalität ermöglicht. Dann können wir die Funktion calc() aufrufen:

BinaryOperator op = (BinaryOperator) OperatorManager.getOperator( OperatorManager.ADD ); 
System.out.println( op.calc( 12, 34 ) );

So verbirgt sich hinter jeder ID eine Funktion, die wie ein Funktionszeiger verwendet werden kann. Noch interessanter ist es, die Funktionen in einen Assoziativspeicher einzusetzen und dann über einen Namen zu erfragen. Diese Implementierung nutzt kein Feld, sondern eine Datenstruktur Map. Eine Erweiterung der Idee nutzt dann auch gleich Enums und EnumMap zur Assoziation zwischen Aufzählung und Funktion.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.





 <<   zurück



Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de